﻿using System;
using System.Collections.Generic;
using System.DirectoryServices.AccountManagement;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Red.Controls;
using Red.Controls.Shell;
using Red.Controls.ViewModels;
using Red.Core.Extensions;
using Red.Launcher.Utilities;
using Database = Red.Launcher.ViewModels.Workspaces.Database;
using Dialog = Red.Launcher.Utilities.Dialog;
using P4 = Red.Core.Perforce;
using Sql = Red.Launcher.ViewModels.Workspaces.Database.Sql;
using Tree = Red.Launcher.ViewModels.Workspaces.Admin.Tree;

namespace Red.Launcher.Menus.Workspaces.Admin
{
    public partial class WorkspaceAdminDialog : RedWindow, Dialog.IEvents, Dialog.IData
    {
        #region Private Types

        private class DummyRecord : Database.Record
        {
            #region Private Data

            private Database.Metadata _metadata;

            #endregion Private Data

            #region Database.Record Property overrides

            public override string Icon => string.Empty;

            public override Database.Metadata Metadata => _metadata;

            public override IEnumerable<object> Values => new object[ 0 ];

            #endregion Database.Record Property overrides

            #region C-Tor

            public DummyRecord( Database.Metadata metadata )
            {
                _metadata = metadata;
            }

            #endregion C-Tor
        }

        #endregion Private Types

        #region Private Data

        private bool _okEnabled = true;
        private readonly Tree.Root _root;
        private Dialog.Manager _dialogs = new Dialog.Manager();
        private Dictionary<Type, RecordDetailView> _recordViewControls;

        private Database.Record _activeRecord;
        private Database.VirtualUserGroup _users = new Database.VirtualUserGroup();
        private Database.SuperGroup _groups = new Database.SuperGroup();

        private CommandPalette _picker = new CommandPalette();

        private bool _fetchingAdGroups;
        private readonly ActiveDirectory.AsyncFetcher<GroupPrincipal> _adGroups = new ActiveDirectory.AsyncFetcher<GroupPrincipal>();

        #endregion Private Data

        #region C-Tor

        partial void InitializeAnalytics();
        public WorkspaceAdminDialog()
        {
            InitializeComponent();
            DataContext = this;

            _users.Fetch().DoNotAwait();
            _groups.Fetch().DoNotAwait();
            FetchActiveDirectoryGroups();

            _root = new Tree.Root();
            Database.Record data = new Database.SpecificationsGroup();
            Tree.Item item = new Tree.Item( _root, data );
            _root.Items.Add( item );
            _root.RebuildAsync().DoNotAwait();

            _root.Filter = new Predicate<object>( Filter );
            _filter.TextChanged += _filter_TextChanged;

            _tree.DataContext = _root;

            _recordViewControls = new Dictionary<Type, RecordDetailView>()
            {
                { typeof( Database.SpecificationsGroup ), _specificationsGroupDetailView },
                { typeof( Database.P4ClientSpecification ), _specificationsDetailView },
                { typeof( Database.GroupToClientSpecs ), _assignedDetailView },
                { typeof( Database.UserToClientSpecs ), _assignedDetailView },
                { typeof( Database.ClientParameter ), _parameterDetailView },
            };

            foreach ( KeyValuePair<Type, RecordDetailView> pair in _recordViewControls )
            {
                pair.Value.Visibility = Visibility.Hidden;

                //This merely stops wpf from spewing binding errors into the console whilst the datacontext is still inherited from the dialog
                pair.Value.DataContext = null;
            }

            Closed += Window_Closed;
            Loaded += Window_Loaded;

            _dialogs.Owner = this;

            InitializeAnalytics();
        }

        #endregion C-Tor

        #region Event Handlers

        private void Window_Loaded( object sender, RoutedEventArgs e )
        {
            MinHeight = ActualHeight;
        }

        private void Window_Closed( object sender, EventArgs e )
        {
            _dialogs.CloseAll();
        }

        private async void Ok_Executed( object sender, ExecutedRoutedEventArgs e )
        {
            _okEnabled = false;

            foreach ( Tree.Item child in _root.Items )
            {
                await child.Data.Commit( CommitComplete );
            }
        }

        private void Ok_CanExecute( object sender, CanExecuteRoutedEventArgs e )
        {
            e.CanExecute = _okEnabled;
        }

        private void CommitComplete( DatabaseHelper.Result result )
        {
            if( result == DatabaseHelper.Result.Success )
            {
                Ok?.Invoke();

                Close();
            }
            else if ( result == DatabaseHelper.Result.Sql )
            {
                RedMessageBox.ShowAsync( GetWindow( this ), RedDialogType.Ok, "Cannot submit invalid data from the workspace admin dialog.\nPlease fix it and try again", mode: RedMessageBox.Mode.Warning );
            }
            else if ( result == DatabaseHelper.Result.Connection )
            {
                RedMessageBox.ShowAsync( GetWindow( this ), RedDialogType.Ok, "There was a problem connecting to the database, please wait a moment before trying again", mode: RedMessageBox.Mode.Warning );
            }

            _okEnabled = true;
        }

        private void Cancel_Executed( object sender, ExecutedRoutedEventArgs e )
        {
            Close();
        }

        private void _tree_SelectedItemChanged( object sender, RoutedPropertyChangedEventArgs<object> e )
        {
            if ( e.OldValue != null && e.OldValue.GetType().IsAssignableFrom( typeof( Tree.Item ) ) )
            {
                Tree.Item item = (Tree.Item)e.OldValue;

                RecordDetailView recordDetailView;
                if ( _recordViewControls.TryGetValue( item.Data.GetType(), out recordDetailView ) )
                {
                    recordDetailView.Visibility = Visibility.Hidden;
                }
            }

            if ( e.NewValue != null && e.NewValue.GetType().IsAssignableFrom( typeof( Tree.Item ) ) )
            {
                Tree.Item item = (Tree.Item)e.NewValue;

                RecordDetailView recordDetailView;
                if ( _recordViewControls.TryGetValue( item.Data.GetType(), out recordDetailView ) )
                {
                    recordDetailView.DataContext = item.Data;

                    recordDetailView.Visibility = Visibility.Visible;
                }

                _activeRecord = item.Data;
            }
        }

        private void _tree_PreviewMouseLeftButtonDown( object sender, MouseButtonEventArgs e )
        {
            // Find the specifications object (if applicable)
            Tree.Item item = (Tree.Item)_tree.SelectedItem;
            if ( item?.Data is Database.P4ClientSpecification )
            {
                Specifications specs = _specificationsDetailView.PropertiesContent as Specifications;

                // If the user has entered invalid data, consume the mouse event to prevent the tree selection from changing
                // The user must fix this first
                if ( specs?.IsValid != true )
                {
                    e.Handled = true;
                }
            }
        }

        private void CreateNewSpecification_Click( object sender, RoutedEventArgs e )
        {
            Tree.Item item = (Tree.Item)_tree.SelectedItem;

            if ( item.Data is Database.SpecificationsGroup )
            {
                _dialogs.Show<SpecificationsDialog, Database.P4ClientSpecification>
                (
                    typeof( Database.P4ClientSpecification ),
                    async ( Dialog.IEvents dialog, Dialog.CloseReason reason ) =>
                    {
                        if ( reason == Dialog.CloseReason.Ok )
                        {
                            SpecificationsDialog specificationsDialog = dialog as SpecificationsDialog;

                            Database.Record record = specificationsDialog.P4ClientSpecification;
                            item.Data.AddChild( ref record );
                            await item.RebuildAsync( false, false );
                        }
                    },
                    false,
                    Data
                );
            }
        }

        private static DataGrid GetDatagrid( object sender )
        {
            MenuItem menuItem = sender as MenuItem;

            if ( menuItem != null )
            {
                return menuItem.CommandParameter as DataGrid;
            }

            return null;
        }

        private void EditSpecification_Clicked( object sender, RoutedEventArgs e )
        {
            DataGrid specsGrid = GetDatagrid( sender );
            if ( specsGrid != null )
            {
                Database.P4ClientSpecification specification = (Database.P4ClientSpecification)specsGrid.SelectedItem;

                _dialogs.Show<SpecificationsDialog, Database.P4ClientSpecification>
                (
                    specification.ClientName,
                    specification,
                    ( Dialog.IEvents dialog, Dialog.CloseReason reason ) =>
                    {
                        if ( reason == Dialog.CloseReason.Ok )
                        {
                            SpecificationsDialog specificationsDialog = dialog as SpecificationsDialog;

                            specification.Copy( specificationsDialog.P4ClientSpecification );
                        }
                    },
                    false,
                    Data
                );
            }
        }

        private async void ShowUsers_Clicked( object sender, RoutedEventArgs e )
        {
            DataGrid specsGrid = GetDatagrid( sender );
            if ( specsGrid != null )
            {
                Database.P4ClientSpecification specification = (Database.P4ClientSpecification)specsGrid.SelectedItem;

                Sql.Select.Table usersToClients = Sql.Select.CreateTable( Database.UserToClientSpecs.METADATA.Table, "ucs" );
                Sql.Select.Table users = Sql.Select.CreateTable( Database.User.METADATA.Table, "u" );
                Sql.Select.Join join = Sql.Select.CreateJoin( users, Sql.Select.JoinType.Left );
                join.Conditions.AddVar( "id", Database.User.METADATA.ForeignKey, users, usersToClients );

                Sql.Select query = new Sql.Select();
                query.Sources.Add( usersToClients );
                query.Sources.Add( join );
                query.AddColumn( "*", users );
                query.Conditions.AddLiteral( Database.P4ClientSpecification.METADATA.ForeignKey, specification.Id, usersToClients );

                // We will display the dialog immediately, and then populate it as soon as the data is retrieved by 
                Dialogs.UserList dialog = new Dialogs.UserList();
                dialog.Show();

                DatabaseHelper.FetchItemCallback fetchItemCallback = ( Dictionary<string, object> row ) =>
                {
                    dialog.Users.Add( (Database.User)Database.User.METADATA.CreateFromDatabaseRow( row ) );
                };

                await DatabaseHelper.FetchAsync( query.Sql, Settings.DatabaseConnectionString, fetchItemCallback );
            }
        }

        private async void Delete_Clicked( object sender, RoutedEventArgs e )
        {
            _okEnabled = false;

            DataGrid specsGrid = GetDatagrid( sender );
            if ( specsGrid != null )
            {
                Database.P4ClientSpecification specification = (Database.P4ClientSpecification)specsGrid.SelectedItem;

                if ( RedMessageBox.Show( this, RedDialogType.OkCancel, $"Are you sure you want to delete the workspace specification '{specification.Name}'?\nThis is a dangerous operation!\nIf you are retiring an old specification, you should hide it instead.\nThis should only be used in certain situations (such as deleting a specification created by accident)", "DANGER - Delete workspace?", RedMessageBox.Mode.Warning ) )
                {
                    Tree.Item item = (Tree.Item)_tree.SelectedItem;

                    Database.Record root = item.Data;

                    root.Children.Remove( specification );

                    // We need to find all associated records in DisabledClientSpecs and ImplementedClientSpecs
                    Database.Metadata disabledMetadata = new Database.Metadata() { Table = "DisabledClientSpecs" };
                    Database.Metadata implementedMetadata = new Database.Metadata() { Table = "ImplementedClientSpecs" };

                    string disabledSql = $@"SELECT id FROM [{Settings.DatabaseName}].[dbo].[{disabledMetadata.Table}] WHERE {Database.P4ClientSpecification.METADATA.ForeignKey} = {specification.Id}";
                    string implementedSql = $@"SELECT id FROM [{Settings.DatabaseName}].[dbo].[{implementedMetadata.Table}] WHERE {Database.P4ClientSpecification.METADATA.ForeignKey} = {specification.Id}";

                    // Add all the associated records as temporary children so that they get included up as we traverse the tree on Commit()
                    DatabaseHelper.FetchItemCallback disabledItemCallback = ( Dictionary<string, object> row ) => { specification.Children.Add( new DummyRecord( disabledMetadata ) { Id = (int)row[ "id" ], Name = "Hidden record" } ); };
                    DatabaseHelper.FetchItemCallback implementedItemCallback = ( Dictionary<string, object> row ) => { specification.Children.Add( new DummyRecord( implementedMetadata ) { Id = (int)row[ "id" ], Name = "Implementation record" } ); };

                    await DatabaseHelper.FetchAsyncSafe( disabledSql, Settings.DatabaseConnectionString, disabledItemCallback );
                    await DatabaseHelper.FetchAsyncSafe( implementedSql, Settings.DatabaseConnectionString, implementedItemCallback );
                }
            }

            _okEnabled = true;
        }

        private async void AddCommandLineParam_Click( object sender, RoutedEventArgs e )
        {
            ParamDialog paramDialog = new ParamDialog();

            SetDialogLocation( paramDialog );
            bool result = await paramDialog.ShowAsync();

            if ( result )
            {
                Database.Record record = new Database.ClientParameter()
                {
                    Name = paramDialog.Key,
                    Value = paramDialog.Value
                };

                _activeRecord.AddChild( ref record );
            }
        }

        private void _paramDialog_ParamCreated( Database.ClientParameter param )
        {
            Database.Record record = param;
            _activeRecord.AddChild( ref record );
        }

        private void DeleteChild_Clicked( object sender, RoutedEventArgs e )
        {
            DataGrid childrenGrid = GetDatagrid( sender );

            if ( childrenGrid != null )
            {
                Tree.Item item = (Tree.Item)_tree.SelectedItem;
                Database.Record specification = item.Data;
                Database.Record child = childrenGrid.SelectedItem as Database.Record;
                specification.Children.Remove( child );
            }
        }

        private void AddUser_Click( object sender, RoutedEventArgs e )
        {
            ShowPicker<Database.User, Database.UserToClientSpecs>( _users );
        }

        private void AddGroup_Click( object sender, RoutedEventArgs e )
        {
            ShowPicker<Database.UserGroup, Database.GroupToClientSpecs>( _groups );
        }

        private void ImportADGroup_Click( object sender, RoutedEventArgs e )
        {
            if ( _fetchingAdGroups )
                return;

            List<CommandPaletteItem> items = new List<CommandPaletteItem>();

            foreach ( GroupPrincipal group in _adGroups.Items )
            {
                if ( !group.Guid.HasValue )
                    continue;

                CommandPaletteItem<GroupPrincipal> item = new CommandPaletteItem<GroupPrincipal>( group )
                {
                    Name = group.ToString()
                };

                item.Click += ( object s, GroupPrincipal selectedGroup ) =>
                {
                    Database.Record record = new Database.ADGroupToClientSpecs( selectedGroup );
                    _activeRecord.AddChild( ref record );
                };

                items.Add( item );
            }

            _picker.Items = items;

            SetDialogLocation( _picker );
            _picker.Show();
        }

        private void ItemSelected( object sender, SelectionChangedEventArgs e )
        {
            DataGrid datagrid = sender as DataGrid;

            if ( datagrid != null && datagrid.SelectedItem != null )
            {
                _tree.SelectedItem.IsExpanded = true;
            }
        }
        private void _filter_TextChanged( object sender, TextChangedEventArgs e )
        {
            _root.RefreshFilter();
        }

        private bool Filter( object obj )
        {
            DataItem item = (DataItem)obj;

            return item.Title.ToLower().Contains( _filter.Text.ToLower() );
        }

        #endregion Event Handlers

        #region IEvents

        public bool Propagated { get; set; }
        public event Dialog.InterfaceClosed Ok;

        #endregion IEvents

        #region IData

        private object _data;
        public object Data
        {
            get { return _data; }
            set
            {
                _data = value;

                Specifications specs = _specificationsDetailView.PropertiesContent as Specifications;
                if ( specs != null )
                {
                    specs.Mediator = value as P4.Mediator;
                }
            }
        }

        #endregion IData

        #region Private Methods

        private void SetDialogLocation( Window dialog )
        {
            Point mousePos = PointToScreen( Mouse.GetPosition( this ) );
            dialog.Top = mousePos.Y;
            dialog.Left = mousePos.X;
        }

        private void ShowPicker< TOrignalType, TAssignType >( Database.Record parent )
            where TOrignalType : Database.Record
            where TAssignType : Database.Record
        {
            List<CommandPaletteItem> items = new List<CommandPaletteItem>();

            foreach ( Database.Record child in parent.Children )
            {
                if ( _activeRecord.Children.Contains( child ) )
                    continue;

                CommandPaletteItem<Database.Record> item = new CommandPaletteItem<Database.Record>( child )
                {
                    Name = child.Name
                };

                item.Click += ( object sender, Database.Record selectedChild ) =>
                {
                    Database.Record assignedChild = (Database.Record)Activator.CreateInstance( typeof( TAssignType ), new object[] { (TOrignalType)selectedChild } );

                    _activeRecord.AddChild( ref assignedChild );
                };

                items.Add( item );
            }

            _picker.Items = items;

            SetDialogLocation( _picker );
            _picker.Show();
        }

        private async void FetchActiveDirectoryGroups()
        {
            _fetchingAdGroups = true;
            await _adGroups.Fetch();
            _fetchingAdGroups = false;
        }

        #endregion Private Methods
    }
}
